package com.masq.aboard.filter;

import com.masq.aboard.common.exceptions.InvalidTokenException;
import com.masq.aboard.common.exceptions.NonTokenException;
import com.masq.aboard.common.exceptions.TokenExpireException;
import com.masq.aboard.common.util.JwtTokenProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * JWT过滤器
 * @author masq
 * @since 2024-05-01
 */
public class JwtCustomFilter extends OncePerRequestFilter {

    private static final int BEARER_LENGTH = 7;
    private final JwtTokenProvider jwtTokenProvider;
    private final UserDetailsService userDetailsService;

    private final Set<String> IGNORE_URL = new HashSet<>();

    public JwtCustomFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
        this.jwtTokenProvider = jwtTokenProvider;
        this.userDetailsService = userDetailsService;

        initUrlPatterns();
    }

    private void initUrlPatterns() {
        // 初始化授权URL模式
        IGNORE_URL.add("/manager/login");
        IGNORE_URL.add("/manager/getLoginKey");
    }

    /**
     * 处理过滤器逻辑，用于认证和授权。
     * 从请求头中提取Authorization信息，验证JWT token的有效性，并在验证成功后设置安全上下文。
     *
     * @param request  HttpServletRequest对象，代表客户端的HTTP请求
     * @param response HttpServletResponse对象，用于向客户端发送HTTP响应
     * @param filterChain 过滤器链，允许将请求传递给下一个过滤器或处理器
     * @throws ServletException 如果处理请求时发生Servlet相关异常
     * @throws IOException 如果处理请求时发生IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (skipValidation(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        // 从请求头中获取Authorization信息
        String authorization = request.getHeader("Authorization");
        // 如果没有Authorization信息，则返回令牌不存在错误
        if (!StringUtils.hasLength(authorization)) {
            throw new NonTokenException();
        }

        String username = null;
        String jwtToken = null;

        // 如果Authorization信息以Bearer开头，提取JWT Token
        if (authorization.startsWith("Bearer ")) {
            jwtToken = authorization.substring(BEARER_LENGTH);
            // 检查JWT Token是否已过期
            boolean tokenExpired = jwtTokenProvider.isTokenExpired(jwtToken);
            if (!tokenExpired) {
                // 如果Token未过期，从中提取用户名
                username = jwtTokenProvider.getUsername(jwtToken);
            } else {
                throw new TokenExpireException();
            }
        }

        // 如果提取到用户名，并且安全上下文中没有认证信息，则进行认证处理
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = null;
            try {
                userDetails = userDetailsService.loadUserByUsername(username);
            } catch (Exception e) {
                // 处理用户详情加载异常
                throw new UsernameNotFoundException("未找到用户");
            }

            // 验证Token的有效性
            if (jwtTokenProvider.validateToken(jwtToken, userDetails)) {
                // 如果Token有效，创建并设置认证信息
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            } else {
                // 如果Token无效，则返回令牌无效错误
                throw new InvalidTokenException();
            }
        }

        // 继续处理请求链
        filterChain.doFilter(request, response);
    }

    private boolean skipValidation(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        for (String s : IGNORE_URL) {
            if (requestURI.contains(s)) {
                return true;
            }
        }

        return false;
    }

}
